package com.googlecode.dgwt.client.dijit.base.widget;

import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.Event;
import com.googlecode.dgwt.client.dijit.Dijit;
import com.googlecode.dgwt.client.dijit.Widget;
import com.googlecode.dgwt.client.dijit.base.state.ChildrenState;
import com.googlecode.dgwt.client.dijit.base.state.EventState;
import com.googlecode.dgwt.client.dijit.base.state.WidgetState;
import com.googlecode.dgwt.client.dijit.util.Template;
import com.googlecode.dgwt.client.dijit.util.TemplateHandler;
import com.googlecode.dgwt.client.dojo.Dojo;
import com.googlecode.dgwt.client.dojo.core.DOMElement;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public abstract class WidgetBase extends com.google.gwt.user.client.ui.Widget implements TemplateHandler {

    private final WidgetEngine engine;
    
    
    public WidgetBase() {
        assert (this instanceof Widget) : "WidgetBase can't be extended directly! You should extends Widget";
        engine = createEngine();
    }
    

    protected WidgetEngine createEngine() {
        return new WidgetEngine((Widget) this);
    }
    
    protected WidgetHandler createHandler() {
        return null;
    }
    
    protected final WidgetEngine getEngine() {
        return engine;
    }

    /**
     * This method is responsible to setup the Widget's configuration. <br/>
     * It's the first method called after Widget has been instantiated.
     */
    protected void initialize() {
        getEngine().initialize();
    }

    /**
     * This method is responsible to create the Widget elements and render it's
     * contents. Subclasses that override this method can call the constructor
     * {@link #Widget(boolean)} passing True in parameter <code>ready</code>.
     */
    protected void buildRendering() {
        getEngine().buildRendering();
    }
    
    /**
     * Loads this Widget elements from a template. <br/>
     * This method should typically be called from the 
     * {@link #buildRendering()} method of descendant classes.<br/>
     * <br/>
     * Note that this template will be loaded synchronously.
     * 
     * @param template
     *            a template object
     */
    protected void loadFromTemplate(Template template) {
        getEngine().loadFromTemplate(template, this);
    }
    
    protected void loadFromTemplate(Template template, TemplateHandler handler) {
        getEngine().loadFromTemplate(template, handler);
    }
    
    /**
     * This method is responsible to setup the Widget elements. <br/>
     * Before this method be called, there is no guarantee that this Widget 
     * has any element assigned to.
     */
    protected final void postCreate() {
        getEngine().postCreate();
    }

    /**
     * Event fired after this widget elements has been created.
     */
    protected void onPostCreate() {
        getEngine().onPostCreate();
    }
    
    /**
     * Event fired after the {@link Dijit} module has been loaded.
     */
    protected void onStartup() {
        getEngine().onStartup();
    }

    /**
     * Checks if this Widget is ready to be used.
     * @return True if {@link #postCreate()} is already called
     */
    public boolean isReady(){
        return getEngine().isReady();
    }

    /**
     * Checks if this Widget is started.
     * 
     * @return True if this Widget was marked as started by {@link Dijit} module
     */
    public final boolean isStarted() {
        return getEngine().isStarted();
    }

    public String getWidgetId() {
        return getEngine().widgetId;
    }

    /**
     * Add an {@link WidgetState} to this Widget
     * @param state a WidgetState object
     */
    public void addState(WidgetState state) {
        getEngine().addState(state);
    }

    /**
     * Removes an {@link WidgetState} from this Widget
     * @param state a WidgetState object
     */
    public void removeState(WidgetState state) {
        getEngine().removeState(state);
    }

    /**
     * Checks if this Widget has a state
     * @param state a WidgetState object
     * @return True if it's present
     */
    public boolean hasState(WidgetState state) {
        return getEngine().hasState(state);
    }
    
    /**
     * Add an {@link EventState} to be used by this Widget
     * and sink the related events assigned to the state.
     * @see #sinkEvents(int)
     * @param state an {@link EventState} object
     */
    public void requireEventState(EventState state) {
        getEngine().requireEventState(state);
    }
    
    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user.client.Event)
     */
    @Override
    public void onBrowserEvent(Event event) {
        getEngine().onBrowserEvent(event);
    }
    
    void inheritedOnBrowserEvent(Event event) {
        super.onBrowserEvent(event);
    }
    
    @Override
    protected void doAttachChildren() {
        getEngine().doAttachChildren();
    }

    void inheritedDoAttachChildren() {
        super.doAttachChildren();
    }

    @Override
    protected void doDetachChildren() {
        getEngine().doDetachChildren();
    }

    void inheritedDoDetachChildren() {
        super.doDetachChildren();
    }

    @Override
    protected void setElement(com.google.gwt.user.client.Element elem) {
        super.setElement(elem);
    }

    /**
     * Gets the element responsible for an attribute of this Widget.
     * 
     * @param name
     *            the attribute name
     *            
     * @see #getAttachedElement(String)
     * 
     * @return the name of an {@link Element} attached to this
     */
    public String getElementForAttribute(String name) {
        return getEngine().getElementForAttribute(name);
    }

    /**
     * Assigns an element name of any attached elements in this Widget to be
     * responsible for handle the specified attribute
     * 
     * @param name
     *            the attribute name
     *            
     * @see #getAttachedElement(String)
     * @see #setAttachedElement(String, Element)
     * 
     * @param element
     *            the name of an {@link Element} attached to this
     */
    protected void setElementForAttribute(String name, String element) {
        getEngine().setElementForAttribute(name, element);
    }
    
    /* (non-Javadoc)
     * @see com.googlecode.dgwt.client.dijit.util.TemplateHandler#getTemplateParameter(java.lang.String)
     */
    @Override
    public String getTemplateParameter(String name) {
        if ("widgetid".equals(name)) {
            return getWidgetId();
        }
        return null;
    }

    /**
     * Gets an {@link Element} attached to this Widget.
     * 
     * @param name
     *            the element name. e.g: "domElement", "styleElement",
     *            "sizeableElement", etc...
     * 
     * @see #getElement()
     * @see #getStyleElement()
     * @see #getSizeableElement()
     * @see #getContainerElement()
     *            
     * @return the {@link Element} object, or the root element ({@link #getElement()})  if
     *         there is no such element for the specified name
     */
    public Element getAttachedElement(String name) {
        return getEngine().getAttachedElement(name);
    }

    public Element getAttachedElement(String name, Element alternativeElement) {
        return getEngine().getAttachedElement(name, alternativeElement);
    }

    public boolean hasAttachedElement(String name) {
        return getEngine().hasAttachedElement(name);
    }

    /**
     * Attach an element to this Widget. <br/>
     * <br/>
     * Note that it should be a child on the root {@link #domElement} children
     * tree. <br/>
     * Otherwise, it will not receive events from GWT be calling
     * {@link #sinkEvents(int)}.
     * 
     * @param name
     *            the element name. e.g: "domElement", "styleElement",
     *            "sizeableElement", etc...
     * @param element
     *            the {@link Element} object to attach
     */
    protected void setAttachedElement(String name, Element element) {
        getEngine().setAttachedElement(name, element);
    }
    
    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.UIObject#getElement()
     */
    @Override
    public DOMElement getElement() {
        
        Element domElement = getEngine().getDomElement();
        
        if (domElement == null) {
            
            if (!isReady()){
                // widget is creating it's elements yet
                return null;
            }
            
            // will throws an AssertionError
            return super.getElement().cast();
        }
        
        // Note that this.domElement will be always 
        // equals super.getElement() after calling postCreate();
        
        return domElement.cast();
    }

    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.UIObject#addStyleName(java.lang.String)
     */
    @Override
    public void addStyleName(String styleClass) {
        Dojo.addClass(getElement(), styleClass);
    }
    
    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.UIObject#removeStyleName(java.lang.String)
     */
    @Override
    public void removeStyleName(String styleClass) {
        Dojo.removeClass(getElement(), styleClass);
    }
    
    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
     */
    @Override
    public void setStyleName(String style) {
        getElement().setClassName(style);
    }

    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.UIObject#setStylePrimaryName(java.lang.String)
     */
    @Override
    public void setStylePrimaryName(String newStylePrimaryName) {
        getEngine().setStylePrimaryName(newStylePrimaryName);
    }

    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.UIObject#getStyleName()
     */
    @Override
    public String getStyleName() {
        String styleName = getElement().getClassName();
        return styleName == null ? "" : styleName;
    }
    
    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.UIObject#getStylePrimaryName()
     */
    @Override
    public String getStylePrimaryName() {
        return getEngine().getStylePrimaryName();
    }

    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.UIObject#addStyleDependentName(java.lang.String)
     */
    @Override
    public void addStyleDependentName(String styleSuffix) {
        if (styleSuffix != null && !"".equals(styleSuffix)) {
            String separator = Dijit.getTheme().getDependentSeparator();
            addStyleName(Dijit.BASE_CLASSNAME + separator + styleSuffix + " " +
                         getStylePrimaryName() + separator + styleSuffix);
        }
    }

    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.UIObject#removeStyleDependentName(java.lang.String)
     */
    @Override
    public void removeStyleDependentName(String styleSuffix) {
        if (styleSuffix != null && !"".equals(styleSuffix)) {
            String separator = Dijit.getTheme().getDependentSeparator();
            removeStyleName(Dijit.BASE_CLASSNAME + separator + styleSuffix + " " +
                            getStylePrimaryName() + separator + styleSuffix);
        }
    }
    
    /**
     * Finalize the attachment of a Widget to this. This method is the
     * <b>last</b> step in adding or inserting a Widget into another one, and should
     * be called after physical attachment in the DOM is complete. This Widget
     * becomes the parent of the child Widget, and the child will now fire its
     * {@link Widget#onAttach()} event if this Widget is currently attached.
     * 
     * @param child the widget to be adopted
     * @see #add(Widget)
     */
    protected final void adopt(com.google.gwt.user.client.ui.Widget child) {
        assert (child.getParent() == null);
        childSetParent(child, this);
        if (hasState(ChildrenState.get())){
            ChildrenState.get().updateChild(this, child, true);
        }
    }

    /**
     * This method must be called as part of the remove method of any Widget. It
     * ensures that the Widget's parent is cleared. This method should be called
     * after verifying that the child Widget is an existing child of the Widget,
     * but before physically removing the child Widget from the DOM. The child
     * will now fire its {@link Widget#onDetach()} event if this Widget is
     * currently attached.
     * 
     * @param child the widget to be disowned
     * @see #add(Widget)
     */
    protected final void orphan(com.google.gwt.user.client.ui.Widget child) {
        assert (child.getParent() == this);
        childSetParent(child, null);
        if (hasState(ChildrenState.get())){
            ChildrenState.get().updateChild(this, child, false);
        }
    }

    private native void childSetParent(com.google.gwt.user.client.ui.Widget child, com.google.gwt.user.client.ui.Widget widget) /*-{
        child.@com.google.gwt.user.client.ui.Widget::setParent(Lcom/google/gwt/user/client/ui/Widget;)(widget);
    }-*/;
    
    
    protected native void childOnAttach(com.google.gwt.user.client.ui.Widget child) /*-{
        child.@com.google.gwt.user.client.ui.Widget::onAttach()();
    }-*/;
    
    
    protected native void childOnDetach(com.google.gwt.user.client.ui.Widget child) /*-{
        child.@com.google.gwt.user.client.ui.Widget::onDetach()();
    }-*/;

}
